package com.hubujun.driverlicense.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

import android.app.Application;
import android.content.pm.ApplicationInfo;
import android.os.Environment;
import android.os.Process;
import android.text.format.DateFormat;
import android.util.Log;

import com.hubujun.driverlicense.ApplicationContext;


/**
 * A more natural android logging facility.
 * 
 * Usage Examples:
 * 
 * Ln.v("hello there"); Ln.d("%s %s", "hello", "there"); Ln.e( exception, "Error during some operation"); Ln.w( exception, "Error during %s operation", "some other");
 * 
 * 
 */
public class Ln {

	public static class BaseConfig {

		protected int minimumLogLevel = Log.VERBOSE;

		protected String packageName = "";

		protected String scope = "";

		protected BaseConfig() {
		}

		public BaseConfig(Application context) {
			try {
				packageName = context.getPackageName();
				final int flags = context.getPackageManager().getApplicationInfo(packageName, 0).flags;
				minimumLogLevel = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 ? Log.VERBOSE : Log.INFO;
				scope = packageName.toUpperCase();

				Ln.d("Configuring Logging, minimum log level is %s", logLevelToString(minimumLogLevel));

			} catch (Exception e) {
				Log.e(packageName, "Error configuring logger", e);
			}
		}

		public int getLoggingLevel() {
			return minimumLogLevel;
		}

		public void setLoggingLevel(int level, boolean isLogInFile) {
			minimumLogLevel = level;
			if (isLogInFile) {
				initLogFile();
			}
		}
	}

	/**
	 * config is initially set to BaseConfig() with sensible defaults, then replaced by BaseConfig(ContextSingleton) during guice static injection pass.
	 */
	protected static BaseConfig config = new BaseConfig();

	/** Default implementation logs to android.util.Log */
	public static class Print {

		public int println(int priority, String msg) {
			String tag = getScope(5);
			String message = processMessage(msg);
			if (pw != null) {
				long time = System.currentTimeMillis();
				long mills = time % 1000;
				String millstring = "";
				if (mills > 100) {
					millstring = String.valueOf(mills);
				} else if (mills > 10) {
					millstring = "0" + mills;
				} else {
					millstring = "00" + mills;
				}
				StringBuilder builder = new StringBuilder(DateFormat.format("yyyy-MM-dd kk:mm:ss", time) + "." + millstring + " ");
				builder.append(logLevelToString(priority)).append('/').append(tag).append("(" + Process.myPid() + ")").append(": ").append(message);
				try {
					pw.println(builder.toString());
					pw.flush();
				} catch (Exception e) {
					Log.e("DriverLicense", "write log err", e);
				}
			}
			return Log.println(priority, tag, message);
		}

		protected String processMessage(String msg) {
			if (config.minimumLogLevel <= Log.DEBUG)
				msg = String.format("%s %s", Thread.currentThread().getName(), msg);
			return msg;
		}

		protected static String getScope(int skipDepth) {
			if (config.minimumLogLevel <= Log.DEBUG) {
				StackTraceElement[] stacks = Thread.currentThread().getStackTrace();
				if (skipDepth > stacks.length - 1)
					skipDepth = stacks.length - 1;
				final StackTraceElement trace = stacks[skipDepth];
				return config.scope + "/" + trace.getFileName() + ":" + trace.getLineNumber();
			}

			return config.scope;
		}

	}

	/**
	 * print is initially set to Print(), then replaced by guice during static injection pass. This allows overriding where the log message is delivered to.
	 */
	protected static Print print = new Print();

	private Ln() {
	}

	public static int v(Throwable t) {
		return config.minimumLogLevel <= Log.VERBOSE ? print.println(Log.VERBOSE, Log.getStackTraceString(t)) : 0;
	}

	public static int v(Object format, Object... args) {
		if (config.minimumLogLevel > Log.VERBOSE)
			return 0;

		final String s = format.toString();
		final String message = args.length > 0 ? String.format(s, args) : s;
		return print.println(Log.VERBOSE, message);
	}

	public static int v(Throwable throwable, Object format, Object... args) {
		if (config.minimumLogLevel > Log.VERBOSE)
			return 0;

		final String s = format.toString();
		final String message = (args.length > 0 ? String.format(s, args) : s) + '\n' + Log.getStackTraceString(throwable);
		return print.println(Log.VERBOSE, message);
	}

	public static int d(Throwable t) {
		return config.minimumLogLevel <= Log.DEBUG ? print.println(Log.DEBUG, Log.getStackTraceString(t)) : 0;
	}

	public static int d(Object format, Object... args) {
		if (config.minimumLogLevel > Log.DEBUG)
			return 0;

		final String s = format.toString();
		final String message = args.length > 0 ? String.format(s, args) : s;
		return print.println(Log.DEBUG, message);
	}

	public static int d(Throwable throwable, Object format, Object... args) {
		if (config.minimumLogLevel > Log.DEBUG)
			return 0;

		final String s = format.toString();
		final String message = (args.length > 0 ? String.format(s, args) : s) + '\n' + Log.getStackTraceString(throwable);
		return print.println(Log.DEBUG, message);
	}

	public static int i(Throwable t) {
		return config.minimumLogLevel <= Log.INFO ? print.println(Log.INFO, Log.getStackTraceString(t)) : 0;
	}

	public static int i(Object format, Object... args) {
		if (config.minimumLogLevel > Log.INFO)
			return 0;

		final String s = format.toString();
		final String message = args.length > 0 ? String.format(s, args) : s;
		return print.println(Log.INFO, message);
	}

	public static int i(Throwable throwable, Object format, Object... args) {
		if (config.minimumLogLevel > Log.INFO)
			return 0;

		final String s = format.toString();
		final String message = (args.length > 0 ? String.format(s, args) : s) + '\n' + Log.getStackTraceString(throwable);
		return print.println(Log.INFO, message);
	}

	public static int w(Throwable t) {
		return config.minimumLogLevel <= Log.WARN ? print.println(Log.WARN, Log.getStackTraceString(t)) : 0;
	}

	public static int w(Object format, Object... args) {
		if (config.minimumLogLevel > Log.WARN)
			return 0;

		final String s = format.toString();
		final String message = args.length > 0 ? String.format(s, args) : s;
		return print.println(Log.WARN, message);
	}

	public static int w(Throwable throwable, Object format, Object... args) {
		if (config.minimumLogLevel > Log.WARN)
			return 0;

		final String s = format.toString();
		final String message = (args.length > 0 ? String.format(s, args) : s) + '\n' + Log.getStackTraceString(throwable);
		return print.println(Log.WARN, message);
	}

	public static int e(Throwable t) {
		return config.minimumLogLevel <= Log.ERROR ? print.println(Log.ERROR, Log.getStackTraceString(t)) : 0;
	}

	public static int e(Object format, Object... args) {
		if (config.minimumLogLevel > Log.ERROR)
			return 0;

		final String s = format.toString();
		final String message = args.length > 0 ? String.format(s, args) : s;
		return print.println(Log.ERROR, message);
	}

	public static int e(Throwable throwable, Object format, Object... args) {
		if (config.minimumLogLevel > Log.ERROR)
			return 0;

		final String s = format.toString();
		final String message = (args.length > 0 ? String.format(s, args) : s) + '\n' + Log.getStackTraceString(throwable);
		return print.println(Log.ERROR, message);
	}

	public static boolean isDebugEnabled() {
		return config.minimumLogLevel <= Log.DEBUG;
	}

	public static boolean isVerboseEnabled() {
		return config.minimumLogLevel <= Log.VERBOSE;
	}

	public static BaseConfig getConfig() {
		return config;
	}

	public static String logLevelToString(int loglevel) {
		switch (loglevel) {
		case Log.VERBOSE:
			return "VERBOSE";
		case Log.DEBUG:
			return "DEBUG";
		case Log.INFO:
			return "INFO";
		case Log.WARN:
			return "WARN";
		case Log.ERROR:
			return "ERROR";
		case Log.ASSERT:
			return "ASSERT";
		}

		return "UNKNOWN";
	}

	private static PrintWriter pw = null;

	private static void initLogFile() {
		if (config.minimumLogLevel <= Log.DEBUG) {
			File file = new File((Environment.getExternalStorageDirectory() + "/183/" + ApplicationContext.getCurProcessName() + "_log.txt").replace(':', '_'));
			if (!file.exists()) {
				file.getParentFile().mkdirs();
				try {
					file.createNewFile();
				} catch (IOException e) {
					Log.e("DriverLicense", "init log file err: " + file, e);
				}
			}
			if (file.exists()) {
				try {
					FileWriter fw = new FileWriter(file, true);
					pw = new PrintWriter(fw);
				} catch (FileNotFoundException e) {
					Log.e("DriverLicense", "init log file stream err: " + file, e);
				} catch (IOException e) {
					Log.e("DriverLicense", "init log file stream err: " + file, e);
				}
			}

			Thread.setDefaultUncaughtExceptionHandler(MyCrashHandler.getInstance());
		}
	}

}
